package BESA.SystemLevel.transactions.gcc;

import java.rmi.RemoteException;
import java.util.Hashtable;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import BESA.Agent.Event.EventBESA;
import BESA.SystemLevel.AdmBESA;
import BESA.SystemLevel.transactions.agent.TransactionalEventBESA;
import BESA.Exception.ExceptionBESA;
import BESA.SystemLevel.Directory.AgHandler;
import BESA.SystemLevel.Directory.AgHandlerBESA;
import BESA.SystemLevel.transactions.agent.Intention;
import BESA.SystemLevel.transactions.agent.TransactionalAgent;
import BESA.SystemLevel.transactions.gca.GCCGCA;
import BESA.util.Log;
import BESA.util.TransactionState;
import BESA.util.VisualSetUp;

import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import sample.test.cc.cool.gui.controllers.AgentContainerController;
import sample.test.cc.simpleagent.SimpleState;

public class GCC_Impl extends UnicastRemoteObject implements GCC {

	private static final long serialVersionUID = 8957682462688898661L;
	public static String RMI_NAME;
    /**
     * Map <idTransaction, Map <agentAlias, informacion relacionada a la copia del
     * agente de agentAlias, y la lista de intenciones>>
     */
    private Map<Long, Map<String, CopyData>> copiesPerTransaction;
    private GCCGCA gca;
    private String GCC_RMIURL;
    //Tiempo Logico de Transaccion que fue ultima en validar recurso <agAlias, tiempo logico transaccion>
    private Long lastCommited;
    private List<Long> workingTransactionsID;
    /*
     * Transaction ID x Validation Number of Commited Transactions used in validation phase of uncommited transactions
     */
    private Map<Long, Long> tIdPerValNumber_CommitedTransactions;
    private Log log;

    public GCC_Impl(String GCC_RMIURL) throws RemoteException {
        super();
        this.GCC_RMIURL = GCC_RMIURL;
        log = new Log("GCC LOG");
        init();
    }

    private void init() {
        copiesPerTransaction = new Hashtable<Long, Map<String, CopyData>>();
        tIdPerValNumber_CommitedTransactions = new Hashtable<Long, Long>();
        lastCommited = -2l; //Asignacion de numeros de validacion comienza desde 0
        workingTransactionsID = new ArrayList<Long>();
    }

    public synchronized void abort(long idTransaction) throws RemoteException {
        log.addLine("----------ABORT: "+idTransaction);
        this.copiesPerTransaction.remove(idTransaction); // Borra las copias de la transaccion
        if(VisualSetUp.VISUAL){
        	AgentContainerController.getInstance().updateStatusTrasaction(idTransaction, TransactionState.Aborted.toString());
        }
    }

    public synchronized void commit(long idTransaction, long validationNumber) throws RemoteException {

        log.addLine("----------COMMIT");

        Set<String> agAliasKeys = this.copiesPerTransaction.get(idTransaction).keySet();
        for (Iterator<String> it = agAliasKeys.iterator(); it.hasNext();) {
            String agAlias = it.next();
            CopyData copy = this.copiesPerTransaction.get(idTransaction).get(agAlias);
            //Se reemplaza en el handler que contiene el recurso, el original por la copia
            log.addLine("ESTADO AGENTE " + agAlias + " ANTES DEL COMMIT:: " + ((SimpleState) copy.agHandler.getAg().getStateAgent()).getValue());
            copy.agHandler.setAgent(copy.copyHandler.getAg());
            log.addLine("ESTADO AGENTE " + agAlias + " DESPUÉS DEL COMMIT:: " + ((SimpleState) copy.agHandler.getAg().getStateAgent()).getValue());
            if(VisualSetUp.VISUAL){
            	// UPDATE TEH AGENT TABLE WITH THE NEW VALUES
            }
        }
        tIdPerValNumber_CommitedTransactions.put(validationNumber, idTransaction);

        lastCommited = validationNumber;
        log.addLine("------------------------------------------------------------------------------------");
        //Limpiar Copias y Registros
        //Borrar copias innecesarias si no existen transacciones concurrentes
        if (workingTransactionsID.isEmpty()) {
            log.addLine("----------BORRANDO COPIAS: NO EXISTEN TRANSACCIONES CONCURRENTES");
            init();
        }
        if(VisualSetUp.VISUAL){
        	AgentContainerController.getInstance().updateStatusTrasaction(idTransaction, TransactionState.Commited.toString());
        }

    }

    public synchronized void eventManager(AgHandler agH, TransactionalEventBESA evTransactional, Intention intention) {
        log.addLine("----------INICIO DE EVENT MANAGER");

        long idTransaction = evTransactional.getIdTransaction();
        workingTransactionsID.add(idTransaction);

        copyManager(agH, idTransaction);

        CopyData copyData = this.copiesPerTransaction.get(idTransaction).get(agH.getAlias());
        //Send Event al Handler de la Copia
        AgHandler agHCopy = copyData.copyHandler;
        EventBESA evBESA = evTransactional.getNonTransactionalEventBESA();
        try {
            agHCopy.sendEvent(evBESA);
        } catch (ExceptionBESA ex) {
            Logger.getLogger(GCC_Impl.class.getName()).log(Level.SEVERE, null, ex);
        }

        //Modificar intencion de la copia
        if (intention.equals(Intention.READWRITE) || intention.equals(Intention.READ)) {
            copyData.hasBeenRead = true;
        }
        if (intention.equals(Intention.READWRITE) || intention.equals(Intention.WRITE)) {
            copyData.hasBeenWritten = true;
        }
        if(VisualSetUp.VISUAL){
        	AgentContainerController.getInstance().updateGCCTable(idTransaction,copyData.agHandler.getAlias(), copyData.agHandler.getAgId(), intention.toString(),"-");
        }

        log.addLine("PROCESANDO EVENTO en agente " + agH.getAlias() + " en el evento " + evBESA.getType() + " con la intención de " + intention.toString());
    }

    /**
     * Verifica si existe la transaccion y si existe la copia para el recurso en la Transaccion...
     * en el caso que no exista la transaccion debe crearla y hacer el join
     * en el caso que no exista la copia... debe crear la copia para la transaccion
     *
     * @param agH
     * @param evTransactional
     */
    private synchronized void copyManager(AgHandler agH, long idTransaction) {
        boolean createTransactionAndJoin = false;
        boolean createCopy = false;
        // Verificar si la transaccion ya ha sido registrada
        if (this.copiesPerTransaction.containsKey(idTransaction)) {
            // Si dentro de la transaccion no existe una copia del agente
            if (!this.copiesPerTransaction.get(idTransaction).containsKey(agH.getAlias())) {
                createCopy = true;
            }
        } else {
            createTransactionAndJoin = true;
            createCopy = true;
        }
        if (createCopy) {
            log.addLine("CREATE THE COPY " + agH.getAlias());
            //Crea Handler de la copia
            String aliasCopia = agH.getAlias() + "_"+"COPY_" + Long.toString(idTransaction);
            TransactionalAgent agRecurso = (TransactionalAgent) agH.getAg();
            TransactionalAgent agCopia = agRecurso.clone(aliasCopia);
            AgHandler agHCopy = new AgHandler(agCopia.getAlias(), agCopia.getAid(), agCopia, AgHandlerBESA.AG_LOCATION_LOCAL);

            //Crea informacion de la copia
            CopyData copyData = new CopyData();
            copyData.copyHandler = agHCopy;
            copyData.hasBeenRead = false;
            copyData.hasBeenWritten = false;
            /**
             * lastCommited es el numero de validacion del recurso al que se le acaba de sacar copia
             * Las transacciones con las cuales se valida seran entonces las que le siguen allastCommited
             */
            copyData.startValidation = lastCommited + 1;
            copyData.agHandler = agH;
            //Registra la copia en la transaccion
            if (createTransactionAndJoin) {
                log.addLine("CREATE TRANSACTION AND JOIN " + idTransaction);
                Map<String, CopyData> copies = new Hashtable<String, CopyData>();
                copies.put(agH.getAlias(), copyData);
                copiesPerTransaction.put(idTransaction, copies);
                // Hace el join
                try {
                    gca.join(GCC_RMIURL,AdmBESA.getInstance().getAlias(), idTransaction);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            } else {
                copiesPerTransaction.get(idTransaction).put(agH.getAlias(), copyData);
            }

        }
    }

    public synchronized boolean getVote(long idTransaction, long validationNumber) throws RemoteException {
        log.addLine("----------ENTRANDO A FASE DE VALIDACIÓN");
        if(VisualSetUp.VISUAL){
        	AgentContainerController.getInstance().updateStatusTrasaction(idTransaction, TransactionState.Validating.toString());
        }
        workingTransactionsID.remove(idTransaction);
        boolean vote = true;
        boolean individualVote = false;

        //Obtener voto individual de todos los recursos participantes en la transaccion
        // Si alguno vota que no entonces el voto es NO - false , de lo contrario es SI - true
        Set<String> voters = this.copiesPerTransaction.get(idTransaction).keySet();
        for (Iterator<String> it = voters.iterator(); it.hasNext();) {
            String individualVoter = it.next();
            CopyData copy = this.copiesPerTransaction.get(idTransaction).get(individualVoter);
            /**
             * Se debe validar hasta la ultima transaccion que consumo cuando se entra a la fase de validacion
             */
            copy.finishValidation = lastCommited;
            individualVote = getIndividualVote(copy);
            log.addLine("VOTO INDIVIDUAL recurso " + copy.agHandler.getAlias() + " voto a " + individualVote);
            
            if(VisualSetUp.VISUAL){
            	AgentContainerController.getInstance().updateGCCTable(idTransaction, copy.agHandler.getAlias(), copy.agHandler.getAgId(), copy.hasBeenRead?"READ":copy.hasBeenWritten?"WRITE":"READ/WRITE", individualVote?"YES":"NO");
            }
            
            if (!individualVote) {
                vote = false;
                break;
            }
        }
        log.addLine("VOTO FINAL :::::::::::::::::: " + vote);
        log.addLine("--------------------------");
        return vote;
    }

    private boolean getIndividualVote(CopyData copyValidated) {
        boolean individualVote = true;

//        //No existe con quien comparar
//        if(copy.startValidation>copy.finishValidation){
//            return true;
//        }

        //numeros de validacion con el cual se deben comparar
        String resourceName = copyValidated.agHandler.getAlias();
        for (Long numValidacion = copyValidated.startValidation; numValidacion <= copyValidated.finishValidation; numValidacion++) {
            //Si transaccion hizo commit y se debe tomar en cuenta... es pq existe en tIdPerValNumber_CommitedTransactions
            if (this.tIdPerValNumber_CommitedTransactions.containsKey(numValidacion)) {
                Long idTransCommited = this.tIdPerValNumber_CommitedTransactions.get(numValidacion);
                //Obtener informacion de la copia, del recurso, de la transaccion con la que se va a validar...
                //Si transaccion trabajo con el recurso existe en copiesPerTransaction
                if (this.copiesPerTransaction.get(idTransCommited).containsKey(resourceName)) {
                    CopyData copyCommited = this.copiesPerTransaction.get(idTransCommited).get(resourceName);
                    //Comparar lista de escrituras consumadas con mis lecturas
                    if (copyCommited.hasBeenWritten && copyValidated.hasBeenRead) {
                        individualVote = false;
                        break;
                    }
                }
            }
        }
        
        return individualVote;
    }

    public void setGCA(GCCGCA gca) {
        this.gca = gca;
    }

    public void imprimirLog() {
        log.print();
    }

    class CopyData {

        protected AgHandler copyHandler;
        protected AgHandler agHandler;
        protected boolean hasBeenRead;
        protected boolean hasBeenWritten;
        protected Long startValidation;
        protected Long finishValidation;
    }
}
